#include <codex/driver/CDX_Driver_Opengles.h>
//#include "PE_ResourceMgr.h"
namespace codex
{
namespace video
{

	NativeWindowType driver::m_windowType = NULL;
	NativeDisplay driver::m_display = 0;
	NativeSurface driver::m_surface = 0;
	NativeContext driver::m_context = NULL;
	int		  driver::m_width = 0;
	int		  driver::m_height = 0;
	tU32	  driver::m_depthBufferID = 0;
	tU32	  driver::m_stencilBufferID = 0;

	void driver::beginScene( void )
	{
		//[EAGLContext setCurrentContext:m_context];
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
		[EAGLContext setCurrentContext:m_context];
		glBindFramebuffer(GL_FRAMEBUFFER, m_surface);
		
	}

	void driver::endScene( void )
	{
		glBindRenderbuffer(GL_RENDERBUFFER, m_display);
		[m_context presentRenderbuffer:GL_RENDERBUFFER];
	}


	tBOOL driver::createDriver( NativeWindowType winType )
	{
        // Get the layer
        //CAEAGLLayer *winType = (CAEAGLLayer *)self.layer;
		m_windowType = winType;
        m_context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
        
        if (!m_context || ![EAGLContext setCurrentContext:m_context])
		{
            return FALSE;
        }
        
		glGenFramebuffers(1, &m_surface);
		glGenRenderbuffers(1, &m_display);
		
		glBindFramebuffer(GL_FRAMEBUFFER, m_surface );
		glBindRenderbuffer(GL_RENDERBUFFER, m_display);
		
		glFramebufferRenderbuffer( GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, m_display );
		// other configs
		setupDriver();
		 
		return TRUE;
	}
	/*
	tU32 driver::loadShader(GLenum type, const char *name, const int* l)
	{
		tU32 shader = glCreateShader( type );

		glShaderSource( shader, 1, &name, l );

		glCompileShader( shader );

		tBOOL bCompiled = FALSE;

		glGetShaderiv( shader, GL_COMPILE_STATUS, &bCompiled );

		if( !bCompiled )
		{
			int leng = 0;
			glGetShaderiv( shader, GL_INFO_LOG_LENGTH, &leng );
			if( leng > 1 )
			{

				char *strBuffer = new char[leng];
				glGetShaderInfoLog( shader, leng, NULL, strBuffer );
				printf("-----------------------------------\n");
				printf("Error: Not compiled shader: \"%s\".\nreason: \n%s\n", (type==GL_VERTEX_SHADER)?"VERTEX":"FRAGMENT", strBuffer );
				printf("-----------------------------------\n");
				delete strBuffer;
				glDeleteShader( shader );
				return 0;
			}
		}

		return shader;
	}

	tU32 driver::setupShader(const char* vertexShaderSrc, const char* fragmentShaderSrc)
	{

		tU32 vsh = 0;
		tU32 fsh = 0;
		
		if( !( vsh = loadShader( GL_VERTEX_SHADER, vertexShaderSrc, NULL ) )
		{
			printf("ERROR: Cannot load Vertex shader: \"%s\".", );
			return = 0;
		}

		if( !( fsh = loadShader( GL_FRAGMENT_SHADER, fragmentShaderSrc, NULL ) )
		{
			printf("ERROR: Cannot load Fragmet shader: \"%s\".", );
			glDeleteShader(vsh);
			return = 0;
		}

		tU32 program = glCreateProgram();

		glAttachShader( program, vsh );
		glAttachShader( program, fsh );

		glLinkProgram( program );

		tBOOL bCompiled = FALSE;

		glGetProgramiv( program, GL_LINK_STATUS, &bCompiled );

		if( !bCompiled )
		{
			int leng = 0;
			glGetProgramiv( program, GL_INFO_LOG_LENGTH, &leng );
			if( leng > 1 )
			{
				char *strBuffer = new char[leng];
				glGetProgramInfoLog( program, leng, NULL, strBuffer );
				printf("Error: Not linking. reason: \n%s\n", strBuffer );
				delete strBuffer;
				glDeleteProgram( program );
				glDeleteShader(vsh);
				glDeleteShader(fsh);
				return 0;
			}
		}

		glDeleteShader(vsh);
		glDeleteShader(fsh);

		return program;
	}

*/
	tU32 driver::createBuffer(GLenum target, void *data, tU32 elementSize, tU32 count)
	{
		tU32 buffer = 0;
		//generate buffer
		glGenBuffers( 1, &buffer );
		//set newlly generated buffer as current
		glBindBuffer( target, buffer );
		//load data to buffer
		glBufferData( target, elementSize * count, data, GL_STATIC_DRAW );
		// return buffer
		return buffer;
	}

	void driver::destroyBuffer( tU32* buffer )
	{
		glDeleteBuffers( 1, buffer );
	}

	tU32 driver::createVertexBuffer( void* data, tU32 size, tU32 count )
	{
		return createBuffer( GL_ARRAY_BUFFER, data, size, count );
	}

	tU32 driver::createIndexBuffer( void* data, tU32 size, tU32 count )
	{
		return createBuffer( GL_ELEMENT_ARRAY_BUFFER, data, size, count );
	}

	void driver::setupDriver( void )
	{
		// Allocate color buffer backing based on the current layer size
		glBindRenderbuffer(GL_RENDERBUFFER, m_display);
		[m_context renderbufferStorage:GL_RENDERBUFFER fromDrawable:m_windowType];
		glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &m_width);
		glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &m_height);
		
		//create depth buffer 
		glGenRenderbuffers( 1, &m_depthBufferID );
		glBindRenderbuffer(GL_RENDERBUFFER, m_depthBufferID);
		glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, m_width, m_height);
		glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, m_depthBufferID);
		
		if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
		{
			NSLog(@"Failed to make complete framebuffer object %x", glCheckFramebufferStatus(GL_FRAMEBUFFER));
			//return NO;
		}
		
		glEnable(GL_DEPTH_TEST);

		glEnable(GL_BLEND);
		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		
		glViewport(0, 0, m_width, m_height);

		glClearColor ( 0.3f, 0.6f, 0.9f, 0.0f );
	}

	void driver::release( void )
	{
		if( m_surface )
		{
			glDeleteFramebuffers(1, &m_surface);
			m_surface = 0;
		}
		
		if( m_display )
		{
			glDeleteFramebuffers(1, &m_display);
			m_display = 0;
		}
		
		if( m_depthBufferID )
		{
			glDeleteFramebuffers(1, &m_depthBufferID);
			m_depthBufferID = 0;
		}
		
		if( [EAGLContext currentContext] == m_context )
			[EAGLContext setCurrentContext:NULL];
		
		//[m_context release];
		m_context = NULL;
		
	}

	//=================================================================================================================
	// Summary: create a texture
	// @data: data of the given texture
	// @width: width of the texture
	// @height: height of the texture
	// @mipmaps: mipmap count for this texture
	// @format: pixel format of the texture
	// @compressed: some platform can load compressed data
	//=================================================================================================================
	tU32 driver::createTexture( const void* data, tU32 width, tU32 height, tU32 bpp, Image::EFormat format, Image::EPixelDataType type, int mipmaps )
	{
		tU32 tex;
		GLenum dataType;
		GLenum dataFormat;
		int level = 0;
		tBOOL compressed = FALSE; 
		
		// generates the image
		glGenTextures( 1, &tex );
		glBindTexture( GL_TEXTURE_2D, tex );
		
		
		// TODO: if format is PVRTC 2 or 4, add support for compressed data.
		switch(format)
		{
			case Image::eFMT_ALPHA: dataFormat = GL_ALPHA; break;
			case Image::eFMT_LUMINANCE: dataFormat = GL_LUMINANCE; break;
				
			case Image::eFMT_LUMINANCE_ALPHA: dataFormat = GL_LUMINANCE_ALPHA; break;
			case Image::eFMT_RGB: dataFormat = GL_RGB; break;
			case Image::eFMT_RGBA: dataFormat = GL_RGBA; break;
            default:
                
                break;
		}
		
		switch(type)
		{
			case Image::ePDT_UBYTE: dataType = GL_UNSIGNED_BYTE; break;
			case Image::ePDT_USHORT_4444: dataType = GL_UNSIGNED_SHORT_4_4_4_4; break;
			case Image::ePDT_USHORT_5551: dataType = GL_UNSIGNED_SHORT_5_5_5_1; break;
			case Image::ePDT_USHORT_565: dataType = GL_UNSIGNED_SHORT_5_6_5; break;
            default:
                
                break;
		}
		
		tBYTE* ptr = (tBYTE*)data;
		while( 0 != (mipmaps + 1) )
		{
			//mipmap loading
			if( compressed )
				glCompressedTexImage2D( GL_TEXTURE_2D, level, dataFormat, width, height, 0, (width*height*bpp) / 8, (const void*)ptr );
			else
				glTexImage2D( GL_TEXTURE_2D, level, dataFormat, width, height, 0, dataFormat, dataType, (const void*)ptr );
			
			// calculate offset
			ptr += (width * height * bpp) / 8;
			width /= 2;
			height /= 2;
			
			level++;
			mipmaps--;
		}
		
		glBindTexture( GL_TEXTURE_2D, 0 );
		return tex;
	}
	
	//=================================================================================================================
	// Summary: bind a texture
	// @textureID: texture name to bound
	//=================================================================================================================
	tBOOL driver::bindTexture(tU32 textureID)
	{
		glBindTexture( GL_TEXTURE_2D, textureID );
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
		return TRUE;
	}
	
	//=================================================================================================================
	// Summary: unbind a texture
	//=================================================================================================================
	void driver::unBindTexture( void )
	{
		glBindTexture( GL_TEXTURE_2D, 0 );
	}
	

}
}